package com.zhaojk.audio.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.RateLimiter;
import com.zhaojk.audio.config.ElevenTTSConfig;
import com.zhaojk.audio.constant.CommonConstant;
import com.zhaojk.audio.domain.T2ARequest;
import com.zhaojk.audio.domain.TTSRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.GZIPInputStream;

@Slf4j
@Service
public class ElevenAudioServiceImpl  implements AudioService {
    // 限流：每秒钟2次请求
    private RateLimiter rateLimiter;
    // 重试配置：最大重试次数、初始重试延迟（毫秒）
    private static final int MAX_RETRIES = 3;
    // 1秒
    private static final long INITIAL_RETRY_DELAY_MS = 1000;

    private final ElevenTTSConfig elevenTTSConfig;
    private final ObjectMapper objectMapper;

    @Autowired
    public ElevenAudioServiceImpl(ElevenTTSConfig elevenTTSConfig, ObjectMapper objectMapper) {
        this.elevenTTSConfig = elevenTTSConfig;
        this.objectMapper = objectMapper;
    }
    /**
     * 初始化限流组件
     */
    @PostConstruct
    public void init() {
        // 每秒2个请求
        rateLimiter = RateLimiter.create(2.0);
    }

    @Override
    public byte[] generateAudioFromText(String text) {
        return new byte[0];
    }

    @Override
    public String generateAudioFileFromText(String text) throws Exception {
        return "";
    }

    @Value("${app.audio-file-path}")
    private String audioFilePath;

    @Override
    /**
     * 根据文本生成音频Base64编码（带缓存逻辑）
     */
    public String generateAudioBase64FromText(String text, int repeat, boolean isRandom) throws Exception {
        // 1. 确保缓存目录存在（使用Path API处理跨平台路径）
        Path cacheDir = Paths.get(audioFilePath);
        if (!Files.exists(cacheDir)) {
            Files.createDirectories(cacheDir);
            log.info("创建音频缓存目录: {}", cacheDir.toAbsolutePath());
        }

        // 2. 选择语音ID（随机或默认）
        String voiceId = elevenTTSConfig.getDefaultVoiceId();
        if (isRandom) {
            int voiceIndex = (int) (Math.random() * elevenTTSConfig.getVoiceIds().length);
            voiceId = elevenTTSConfig.getVoiceIds()[voiceIndex];
        }

        // 3. 处理文件名：对text进行MD5哈希，避免特殊字符导致路径错误
        String textHash = DigestUtil.md5Hex(text);
        String cacheFileName = String.format("%s_%d_%s.base64.txt", textHash, repeat, voiceId); // 用下划线分隔，避免CSV格式干扰
        Path cacheFilePath = cacheDir.resolve(cacheFileName); // 使用Path.resolve构建路径，跨平台兼容

        String base64Encoded;

        // 4. 缓存逻辑：文件不存在则请求并保存，存在则直接读取
        if (!Files.exists(cacheFilePath)) {
            // 4.1 发送TTS请求获取Base64编码
            TTSRequest.VoiceSettings voiceSettings = new TTSRequest.VoiceSettings();
            voiceSettings.setStability(0.7);
            //voiceSettings.setSimilarityBoost(1.0);
            TTSRequest request = new TTSRequest();
            request.setText(text);
            request.setModelId(elevenTTSConfig.getModelId());
            request.setVoiceSettings(voiceSettings);
            base64Encoded = textToAudio(request, repeat, voiceId);

            // 4.2 清理Base64字符串（移除可能的换行、空格、引号等无关字符）
            base64Encoded = base64Encoded.replaceAll("[\\n\\r\\s\"]", "");

            // 4.3 保存Base64字符串到缓存文件（使用.txt格式，避免CSV分隔符干扰）
            FileUtil.writeString(base64Encoded, cacheFilePath.toFile(), StandardCharsets.UTF_8);
            log.info("音频Base64缓存文件已创建: {}", cacheFilePath.toAbsolutePath());
        } else {
            // 4.4 从缓存文件读取Base64字符串（完整读取，避免截断）
            base64Encoded = FileUtil.readString(cacheFilePath.toFile(), StandardCharsets.UTF_8);
            log.info("从缓存文件读取Base64: {}", cacheFilePath.toAbsolutePath());
        }

        log.info("生成的Base64长度: {} 字符", base64Encoded.length());
        return base64Encoded;
    }

    private String textToAudio(TTSRequest request, int repeat, String voiceId) throws Exception {
        String url = elevenTTSConfig.getTtsUrl() + voiceId + "?output_format=mp3_44100_128";
        int retryCount = 0;
        long retryDelayMs = INITIAL_RETRY_DELAY_MS;

        while (retryCount <= MAX_RETRIES) {
            try {
                // 步骤1：限流等待（若请求频率超限，阻塞至许可可用）
                // 返回等待时间（秒）
                double waitTime = rateLimiter.acquire();
                log.info("获取API请求许可，等待时间: {}秒", waitTime);

                // 步骤2：构建HTTP请求
                HttpPost httpPost = new HttpPost(url);
                httpPost.setHeader("xi-api-key", elevenTTSConfig.getXiApiKey());
                httpPost.setHeader("Content-Type", "application/json");
                String requestBody = objectMapper.writeValueAsString(request);
                httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));

                // 步骤3：发送请求并处理响应
                try (CloseableHttpClient httpClient = HttpClients.createDefault();
                     CloseableHttpResponse response = httpClient.execute(httpPost)) {

                    int statusCode = response.getStatusLine().getStatusCode();
                    HttpEntity entity = response.getEntity();

                    if (statusCode == HttpStatus.SC_OK) { // 200 OK：正常处理音频
                        if (entity != null) {
                            return processAudioEntity(entity, repeat);
                        } else {
                            throw new IOException("API响应实体为空");
                        }

                    } else if (statusCode == HttpStatus.SC_TOO_MANY_REQUESTS) {
                        // 429：限流重试
                        retryCount++;
                        if (retryCount > MAX_RETRIES) {
                            throw new IOException("触发API限流，已达最大重试次数(" + MAX_RETRIES + ")");
                        }
                        // 优先使用API返回的Retry-After头（单位：秒），无则用指数退避
                        long waitTimeMs = getRetryWaitTime(response, retryDelayMs);
                        log.warn("触发429限流，第{}次重试，等待{}ms后重试", retryCount, waitTimeMs);
                        Thread.sleep(waitTimeMs);
                        // 指数退避（下次延迟翻倍）
                        retryDelayMs *= 2;
                        // 释放资源
                        EntityUtils.consumeQuietly(entity);
                    } else if (statusCode >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
                        // 5xx：服务器错误重试
                        retryCount++;
                        if (retryCount > MAX_RETRIES) {
                            throw new IOException("服务器错误(" + statusCode + ")，已达最大重试次数");
                        }
                        // 随机抖动（0.5-1.5倍延迟）
                        long waitTimeMs = (long) (retryDelayMs * (0.5 + Math.random()));
                        log.warn("服务器错误({})，第{}次重试，等待{}ms后重试", statusCode, retryCount, waitTimeMs);
                        Thread.sleep(waitTimeMs);
                        retryDelayMs *= 2;
                        EntityUtils.consumeQuietly(entity);

                    } else { // 4xx（非429）：客户端错误（如参数错误、认证失败），不重试
                        String errorBody = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : "无响应体";
                        throw new IOException("API请求失败，状态码: " + statusCode + "，响应体: " + errorBody);
                    }
                }

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 恢复中断状态
                throw new Exception("请求被中断", e);
            } catch (IOException e) {
                // IO异常（如网络波动）重试逻辑
                if (retryCount < MAX_RETRIES) {
                    retryCount++;
                    long waitTimeMs = (long) (retryDelayMs * (0.5 + Math.random()));
                    log.warn("IO异常，第{}次重试，等待{}ms后重试: {}", retryCount, waitTimeMs, e.getMessage());
                    Thread.sleep(waitTimeMs);
                    retryDelayMs *= 2;
                } else {
                    throw new Exception("IO异常，已达最大重试次数: " + e.getMessage(), e);
                }
            }
        }
        throw new Exception("超出最大重试次数，无法完成请求");
    }

    /**
     * 处理音频实体：读取二进制数据 → 重复拼接 → 转为Base64
     */
    private String processAudioEntity(HttpEntity entity, int repeat) throws IOException {
        // 处理gzip压缩响应
        boolean isGzipped = entity.getContentEncoding() != null &&
                "gzip".equalsIgnoreCase(entity.getContentEncoding().getValue());

        try (InputStream rawStream = entity.getContent();
             InputStream inputStream = isGzipped ? new GZIPInputStream(rawStream) : rawStream;
             ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) {

            // 读取原始音频字节（mp3格式）
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byteStream.write(buffer, 0, bytesRead);
            }
            byte[] audioBytes = byteStream.toByteArray();
            log.info("原始音频字节长度: {} bytes", audioBytes.length);

            // 处理重复播放：拼接repeat次音频字节（注意：mp3直接拼接可能存在兼容性问题，建议用音频库合并）
            // 避免repeat<=0导致无效
            repeat = Math.max(repeat, 1);
            byte[] repeatedAudioBytes = new byte[audioBytes.length * repeat];
            for (int i = 0; i < repeat; i++) {
                System.arraycopy(audioBytes, 0, repeatedAudioBytes, i * audioBytes.length, audioBytes.length);
            }

            // 转为Base64字符串
            String base64 = Base64Utils.encodeToString(repeatedAudioBytes);
            log.info("重复{}次后Base64长度: {}字符", repeat, base64.length());
            return base64;
        } finally {
            // 确保资源释放
            EntityUtils.consumeQuietly(entity);
        }
    }

    /**
     * 计算重试等待时间：优先使用API返回的Retry-After头，否则用指数退避+随机抖动
     */
    private long getRetryWaitTime(CloseableHttpResponse response, long retryDelayMs) {
        String retryAfter = response.getFirstHeader("Retry-After") != null ?
                response.getFirstHeader("Retry-After").getValue() : null;
        if (retryAfter != null) {
            try {
                return Long.parseLong(retryAfter) * 1000; // 转换为毫秒
            } catch (NumberFormatException e) {
                log.warn("解析Retry-After头失败，使用默认延迟: {}", retryAfter);
            }
        }
        // 指数退避+随机抖动（避免多个请求同时重试导致新的限流）
        return (long) (retryDelayMs * (0.5 + Math.random())); // 0.5-1.5倍当前延迟
    }

    private byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    /**
     * 将 Base64 字符串解码并保存为 MP3 文件
     * @param base64Str 后端接收的 Base64 字符串（需纯净，不含 data URI 前缀）
     * @param outputPath 保存 MP3 的路径（如 "D:/output/audio.mp3"）
     * @throws IOException 文件写入失败
     * @throws IllegalArgumentException Base64 格式错误
     */
    public static void decodeBase64ToMp3(String base64Str, String outputPath) throws IOException {
        // 步骤 1：清理 Base64 字符串（移除可能的换行符、空格、引号等无关字符）
        String cleanBase64 = base64Str.replaceAll("[\\n\\r\\s\"]", ""); // 关键：避免特殊字符干扰解码

        // 步骤 2：Base64 解码（使用 Java 内置解码器，自动处理填充符 =）
        byte[] audioBytes;
        try {
            audioBytes = java.util.Base64.getDecoder().decode(cleanBase64); // 若格式错误，抛出 IllegalArgumentException
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Base64 格式错误：可能包含非法字符或长度不是 4 的倍数", e);
        }

        // 步骤 3：将字节数组写入 MP3 文件（使用 try-with-resources 自动关闭流，确保数据刷新）
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            fos.write(audioBytes); // 写入全部字节
            fos.flush(); // 强制刷新缓冲区（避免数据残留未写入文件）
        }

        System.out.println("MP3 文件保存成功！路径：" + outputPath + "，大小：" + audioBytes.length + " 字节");
    }

}
